﻿#if UNITY_2019_4_OR_NEWER
#define UNITY_3D
#endif

#if UNITY_3D && (UNITY_EDITOR || COMMAND_PROFILER)
#define PROFILING
#endif

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

#if UNITY_3D
using UnityEngine;
#endif
#if PROFILING
using UnityEngine.Profiling;
#endif

#if ADDRESSABLES
using UnityEngine.ResourceManagement.AsyncOperations;
#endif

namespace Devil
{
    using IteratorStack = Stack<IEnumerator>;

    public partial class ParallelDispatcher
    {

        IteratorStack GetIteratorStack()
        {
            return mStackPool.Get();
        }

        void ReleaseStack(IteratorStack stack)
        {
            stack.Clear();
            mStackPool.Add(stack);
        }

        public enum ExecuteThread : byte
        {
            MainThread,
            SubThread,
            Parallel,
        }

        internal abstract class BaseCommand : IYieldInstruction
        {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS

            internal abstract class Buffer<Inst, Cmd, InputType> where Inst : BaseCommand
            {
                int mSize;
                Inst[] mBuffer;
                int mLock;
                internal Buffer(int size)
                {
                    mBuffer = new Inst[size];
                }
                protected abstract Inst NewCommand(int id, Cmd cmd, InputType inputType, BaseCommand dependent);
                protected abstract void Init(Inst inst, Cmd cmd, InputType inputType);
                protected abstract void Release(Inst inst);
                internal void Lock()
                {
                    lock (mBuffer)
                    {
                        mLock++;
                    }
                }
                internal void Unlock()
                {
                    lock (mBuffer)
                    {
                        mLock--;
                    }
                }
                internal Inst Get(int id, Cmd cmd, InputType inputType, BaseCommand dependent)
                {
                    lock (mBuffer)
                    {
                        if (mSize > 0)
                        {
                            var inst = mBuffer[--mSize];
                            mBuffer[mSize] = null;
                            inst._id = id;
                            inst._dependentId = dependent == null ? 0 : dependent._id;
                            inst._dependentOn = dependent;
                            Init(inst, cmd, inputType);
                            return inst;
                        }
                    }
                    return NewCommand(id, cmd, inputType, dependent);
                }

                internal void Cache(Inst inst)
                {
                    lock (mBuffer)
                    {
                        if(mLock == 0 && mSize < mBuffer.Length)
                        {
                            mBuffer[mSize++] = inst;
                            inst._id = 0;
                            inst._dependentId = 0;
                            inst._dependentOn = null;
                            inst.isIndependent = false;
//#if PROFILING
//                            inst.EndSample(true);
//#endif
                            Release(inst);
                        }
                    }
                }

                internal void Release()
                {
                    mSize = 0;
                    mBuffer = null;
                }
            }

            int _dependentId;
            internal BaseCommand dependentOn => _dependentOn != null && _dependentOn._id == _dependentId ? _dependentOn : null;
#else
            internal BaseCommand dependentOn => _dependentOn;
#endif
            int _id;
            BaseCommand _dependentOn;
            internal int id => _id;
            internal abstract bool isComplete { get; }
            internal bool isIndependent;
            internal abstract ExecuteThread executeThread { get; }
            internal BaseCommand(int id, BaseCommand dependent)
            {
                this._id = id;
                this._dependentOn = dependent;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                _dependentId = dependent == null ? 0 : dependent._id;
#endif
            }
            //public override int GetHashCode()
            //{
            //    return id;
            //}
            internal abstract object GetCommandInstance();
            internal bool IsBreakable(bool isByDependent)
            {
                try
                {
                    if (isIndependent && isByDependent)
                        return false;
                    var inst = GetCommandInstance() as IIndependentCommandSignal;
                    return inst == null || inst.IsBreakable(isByDependent);
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                    return true;
                }
            }
            internal virtual bool IsReady
            {
                get
                {
                    var baseCmd = dependentOn;
                    return baseCmd == null || baseCmd.isComplete;
                }
            }
            // 进度值仅作为 UI 系统显示参考，不反应同步的执行状态（因为同步原因显示可能存在滞后）
            internal abstract float GetExecutionProgress(ParallelDispatcher dispatcher);
            internal abstract void Schedule(ParallelDispatcher dispatcher);
            internal abstract void Execute(IThreadWorker worker);
            internal abstract void OnBreak(ParallelDispatcher dispatcher);
            public bool YieldContinue()
            {
                return isComplete;
            }

#if PROFILING

            //internal void DebugAction(string action, string pattern)
            //{
            //    var str = ToString();
            //    if (string.IsNullOrEmpty(pattern) || str.Contains(pattern))
            //        Debug.Log($"[P_{id}] {action}: {str}");
            //}


            //bool _started;
            int _started;
            long _startTime;
            long _waitTime;
            string _sampleName;
            internal void BeginSample(string sampleName)
            {
                var sample = Interlocked.CompareExchange(ref _started, 1, 0);
                if (sample == 0)
                {
                    if (isComplete)
                    {
                        Debug.LogFormat("[{0}] \"{1}\" was already complete.", sampleName, ToString());
                        return;
                    }
                    var t = System.DateTime.UtcNow.Ticks;
                    _startTime = t;
                    _waitTime = t;
                    _sampleName = sampleName;
                }
            }

            protected void ScheduleSample()
            {
                if (_started == 1)
                {
                    _waitTime = System.DateTime.UtcNow.Ticks;
                }
            }

            internal void EndSample(bool abort)
            {
                var sample = Interlocked.CompareExchange(ref _started, 0, 1);
                if (sample == 1)
                {
                    var t = System.DateTime.UtcNow.Ticks;
                    var secs = (t - _startTime) * tick_2_seconds;
                    string info;
                    if (this is WaitCmd)
                    {
                        info = string.Format("[{0}] \"{1}\" was {2}. (total time: {3:0.####}s)",
                              _sampleName, ToString(), abort ? "abort" : "complete", secs);
                    }
                    else if (abort)
                    {
                        info = string.Format("[{0}] \"{1}\" was abort. (total time: {2:0.####}s, wait time: {3:0.####}s)",
                            _sampleName, ToString(), secs, (_waitTime - _startTime) * tick_2_seconds);
                    }
                    else
                    {
                        info = string.Format("[{0}] \"{1}\" was complete. (total time: {2:0.####}s, exec time: {3:0.####}s, wait time: {4:0.####}s)",
                            _sampleName, ToString(), secs, (t - _waitTime) * tick_2_seconds, (_waitTime - _startTime) * tick_2_seconds);
                    }
#if UNITY_3D
                    if (secs > 1)
                    {
                        var red = Mathf.Clamp01((float)(secs * 0.2d));
                        var color = new Color(red, 0.7f - red * 0.5f, 0.5f - red * 0.5f);
                        Debug.LogFormat("<color=#{0}>{1}</color>", ColorUtility.ToHtmlStringRGB(color), info);
                    }
                    else
#endif
                    {
                        Debug.Log(info);
                    }
                }
            }
#endif
        }

        private class SubCmd : BaseCommand
        {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            internal class Buffer : Buffer<SubCmd, ICommand, bool>
            {
                internal Buffer(int size) : base(size) { }
                protected override SubCmd NewCommand(int id, ICommand cmd, bool mainThread, BaseCommand dependent)
                {
                    return new SubCmd(id, mainThread, cmd, dependent);
                }
                protected override void Init(SubCmd inst, ICommand cmd, bool mainThread)
                {
                    Interlocked.Exchange(ref inst.mComplete, 0);
                    inst.mCmd = cmd;
                    inst.mExecThread = mainThread ? ExecuteThread.MainThread : ExecuteThread.SubThread;
#if PROFILING
                    try
                    {
                        if (cmd is IDebugContext ctx)
                            inst.context = ctx.GetContext();
                        else
                            inst.context = cmd as Object;
                        inst.desc = cmd.ToString() ?? cmd.GetType().Name;
                    }
                    catch
                    {
                        inst.desc = cmd.GetType().Name;
                    }
#endif
                }
                protected override void Release(SubCmd inst)
                {
                    inst.mCmd = null;
#if PROFILING
                    inst.context = null;
#endif
                }
            }
#endif

            //SubThreading mSchedule;
            ICommand mCmd;
            ExecuteThread mExecThread;
            int mComplete;
#if PROFILING
            string desc;
            Object context;
#endif
            internal override bool isComplete => mComplete != 0;
            internal override ExecuteThread executeThread { get { return mExecThread; } }
            internal override object GetCommandInstance()
            {
                return mCmd;
            }
            internal SubCmd(int id, bool mainThread, ICommand cmd, BaseCommand dependent) : base(id, dependent)
            {
                mCmd = cmd;
                mExecThread = mainThread ? ExecuteThread.MainThread : ExecuteThread.SubThread;
#if PROFILING
                try
                {
                    if (cmd is IDebugContext ctx)
                        context = ctx.GetContext();
                    else
                        context = cmd as Object;
                    desc = cmd.ToString() ?? cmd.GetType().Name;
                }
                catch
                {
                    desc = cmd.GetType().Name;
                }
#endif
            }

            internal override void OnBreak(ParallelDispatcher dispatcher)
            {
                var wasComplete = Interlocked.CompareExchange(ref mComplete, 1, 0);
                if (wasComplete == 0)
                {
                    try
                    {
                        if (mCmd is IBreakCommandSignal sig)
                            sig.OnBreak();
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogException(e);
                    }
#if PROFILING
                    EndSample(true);
#endif

//#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
//                    dispatcher.mSubCmdsBuffer.Cache(this);
//#endif
                }
            }

            internal override float GetExecutionProgress(ParallelDispatcher threading)
            {
                return isComplete ? 1f : (mCmd is IExecutableProgress proc ? proc.Progress : 0.1f);
            }

            internal override void Schedule(ParallelDispatcher schedule)
            {
                //mSchedule = schedule;
                if (mExecThread == ExecuteThread.MainThread)
                {
                    lock (schedule.mMainCmdQueue)
                    {
                        schedule.mMainCmdQueue.Add(this);
                    }
                }
                else
                {
                    lock (schedule.mSubCmdQueue)
                    {
                        schedule.mSubCmdQueue.Add(this);
                        schedule.StartWorker();
                    }
                }
#if PROFILING
                ScheduleSample();
#endif
                //worker.mExecuting = this;
                //ThreadPool.QueueUserWorkItem(mSchedule.mSubExecutor, this);
            }

            internal override void Execute(IThreadWorker worker)
            {
                //var wasComplete = Interlocked.CompareExchange(ref mComplete, 1, 0);
                if (mComplete == 0)
                {
#if PROFILING
                    Profiler.BeginSample(desc/*, context*/);
#endif
                    try
                    {
                        //threadId = worker.threadId;
                        mCmd.Execute();
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogException(e
#if PROFILING
                            , context
#endif
                            );
                    }
                    finally
                    {
                        var wasComplete = Interlocked.CompareExchange(ref mComplete, 1, 0);
#if PROFILING
                        EndSample(false);
#endif
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                        if (wasComplete == 0)
                            worker.Dispatcher.mSubCmdsBuffer.Cache(this);
#endif
                    }
#if PROFILING
                    Profiler.EndSample();
#endif
                }
            }

#if PROFILING
            public override string ToString()
            {
                return desc;
            }
#endif
        }

        private class ParallelIndex : BaseCommand
        {

#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            internal class Buffer : Buffer<ParallelIndex, ICommandParallelFor<int>, int>
            {
                internal Buffer(int size) : base(size) { }
                protected override void Init(ParallelIndex inst, ICommandParallelFor<int> cmd, int size)
                {
                    inst.mCmd = cmd;
                    inst.mSize = size;
                    inst.mCompleteSize = 0;
                    inst.mExecIndex = 0;
                    inst.mCacheId = 0;
#if PROFILING
                    try
                    {
                        if (cmd is IDebugContext ctx)
                            inst.context = ctx.GetContext();
                        else
                            inst.context = cmd as Object;
                        inst.desc = cmd.ToString() ?? cmd.GetType().Name;
                    }
                    catch
                    {
                        inst.desc = cmd.GetType().Name;
                    }
#endif
                }

                protected override ParallelIndex NewCommand(int id, ICommandParallelFor<int> cmd, int size, BaseCommand dependent)
                {
                    return new ParallelIndex(id, size, cmd, dependent);
                }

                protected override void Release(ParallelIndex inst)
                {
                    inst.mCmd = null;
#if PROFILING
                    inst.context = null;
#endif
                }
            }

            int mCacheId;
#endif


            ICommandParallelFor<int> mCmd;
            int mSize;
            int mCompleteSize;
            int mExecIndex;
            int mBatchNum;
            object mLock;
#if PROFILING
            string desc;
            Object context;
            //long start_time;
#endif
            internal override ExecuteThread executeThread { get { return ExecuteThread.Parallel; } }
            internal override bool isComplete => mCompleteSize >= mSize;
            internal override object GetCommandInstance()
            {
                return mCmd;
            }
            internal ParallelIndex(int id, int count, ICommandParallelFor<int> cmd, BaseCommand dependent) : base(id, dependent)
            {
                mCmd = cmd;
                mSize = count;
                mLock = new object();
#if PROFILING
                try
                {
                    if (cmd is IDebugContext ctx)
                        context = ctx.GetContext();
                    else
                        context = cmd as Object;
                    desc = cmd.ToString() ?? cmd.GetType().Name;
                }
                catch
                {
                    desc = cmd.GetType().Name;
                }
#endif
            }

            internal override void OnBreak(ParallelDispatcher dispatcher)
            {
                Interlocked.Exchange(ref mCompleteSize, mSize);
                try
                {
                    if (mCmd is IBreakCommandSignal)
                        ((IBreakCommandSignal)mCmd).OnBreak();
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                }
#if PROFILING
                EndSample(true);
#endif
            }

            internal override float GetExecutionProgress(ParallelDispatcher threading)
            {
                return mSize > 0 && mCompleteSize < mSize ? (float)mCompleteSize / mSize : 1f;
            }

            internal override void Schedule(ParallelDispatcher schedule)
            {
#if PROFILING
                ScheduleSample();
                //start_time = System.DateTime.UtcNow.Ticks;
#endif
                mBatchNum = ParallelUtils.Max(2, (mSize - 1) / schedule.mWorkers.Length + 1);
                //mExecCount = 0;
                mExecIndex = 0;
                //mSchedule = schedule;
                lock (schedule.mSubCmdQueue)
                {
                    schedule.mSubCmdQueue.Add(this);
                    var len = ParallelUtils.Min(schedule.mWorkers.Length - schedule.mActiveWorkers, ParallelUtils.Max(1, mSize >> 1));
                    while (len > 0 && schedule.StartWorker() != null)
                    {
                        len--;
                    }
                }
                //worker.mExecuting = this;
                //ThreadPool.QueueUserWorkItem(mSchedule.mSubExecutor, this);
            }

            internal override void Execute(IThreadWorker worker)
            {
                //bool finish = false;
#if PROFILING
                Profiler.BeginSample(desc/*, context*/);
#endif
                //int count = Interlocked.Add(ref mExecIndex, mBatchNum);
                //int starti = count - mBatchNum;
                //count = count < mSize ? count : mSize;
                int starti, count;
                lock (mLock)
                {
                    starti = mExecIndex;
                    count = mExecIndex + mBatchNum;
                    if (count > mSize || count <= 0)
                        count = mSize;
                    mExecIndex = count;
                }
                if (count > starti)
                {
                    try
                    {
                        for (int i = starti; mCompleteSize < mSize && i < count; i++)
                        {
                            mCmd.Execute(i);
                        }
                        Interlocked.Add(ref mCompleteSize, count - starti);
                    }
                    catch (System.Exception e)
                    {
                        Interlocked.Exchange(ref mCompleteSize, mSize);
                        Debug.LogException(e
#if PROFILING
                            , context
#endif
                            );
                        Debug.LogErrorFormat("[Parallel] execute prallel cmd ({0}) error.", mCmd.ToString());
                        //finish = !isComplete;
                    }
#if PROFILING
                    if (isComplete)
                        EndSample(false);
#endif

#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                    if (isComplete && Interlocked.CompareExchange(ref mCacheId, 1, 0) == 0)
                        worker.Dispatcher.mParallelCmdsBuffer.Cache(this);
#endif
                }

                //if (finish && mCmd is ICompleteParallelFor)
                //    ((ICompleteParallelFor)mCmd).OnParallelForComplete();
#if PROFILING
                Profiler.EndSample();
                // if (finish)
                //     Debug.LogFormat("Complete \"{0}\" (time: {1:0.######}sec, batch: {2}/{3})",
                //         desc, (System.DateTime.UtcNow.Ticks - start_time) * tick_2_seconds, mBatchNum, mSize);
#endif
            }

#if PROFILING
            public override string ToString()
            {
                return desc;
            }
#endif
        }

        private class ParallelCmd<T> : BaseCommand
        {
            //SubThreading mSchedule;
            ICommandParallelFor<T> mCmd;
            IEnumerable mEnumerable;
            IList<T> mList;
            int mSize;
            int mExecIndex;
            int mCompleteSize;
            int mBatchNum;
            object mLock;
#if PROFILING
            string desc;
            Object context;
            //long start_time;
#endif

            internal override ExecuteThread executeThread { get { return ExecuteThread.Parallel; } }
            internal override bool isComplete => mCompleteSize >= mSize && mSize != -1;
            internal override object GetCommandInstance()
            {
                return mCmd;
            }
            internal ParallelCmd(int id, ICommandParallelFor<T> cmd, IEnumerable enumerable, BaseCommand dependent) : base(id, dependent)
            {
                mCmd = cmd;
                mSize = -1;
                mEnumerable = enumerable;
                mLock = new object();
#if PROFILING
                try
                {
                    if (cmd is IDebugContext)
                        context = ((IDebugContext)cmd).GetContext();
                    else
                        context = cmd as Object;
                    desc = cmd.ToString() ?? cmd.GetType().Name;
                }
                catch
                {
                    desc = cmd.GetType().Name;
                }
#endif
            }

            internal override void OnBreak(ParallelDispatcher dispatcher)
            {
                Interlocked.CompareExchange(ref mSize, 0, -1);
                Interlocked.Exchange(ref mCompleteSize, mSize);
                try
                {
                    if (mCmd is IBreakCommandSignal)
                        ((IBreakCommandSignal)mCmd).OnBreak();
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                }
#if PROFILING
                EndSample(true);
#endif
            }

            internal override float GetExecutionProgress(ParallelDispatcher threading)
            {
                return mSize > 0 && mCompleteSize < mSize ? (float)mCompleteSize / mSize : 1f;
            }

            internal override void Schedule(ParallelDispatcher schedule)
            {
#if PROFILING
                ScheduleSample();
                //start_time = System.DateTime.UtcNow.Ticks;
#endif
                int size;
                if (mEnumerable is IList<T>)
                {
                    mList = (IList<T>)mEnumerable;
                    size = mList.Count;
                }
                else
                {
                    var iter = mEnumerable.GetEnumerator();
                    if (iter != null)
                    {
                        mList = new List<T>();
                        while (iter.MoveNext())
                        {
                            var it = iter.Current;
                            if (it is T t)
                            {
                                mList.Add(t);
                            }
                            //mSize++;
                        }
                        size = mList.Count;
                    }
                    else
                    {
                        size = 0;
                    }
                }
                Interlocked.CompareExchange(ref mSize, size, -1);
                if (mCompleteSize >= mSize)
                    return;
                mBatchNum = ParallelUtils.Max(2, (mSize - 1) / schedule.mWorkers.Length + 1);
                //mExecCount = 0;
                Interlocked.Exchange(ref mExecIndex, 0);
                //mSchedule = schedule;
                lock (schedule.mSubCmdQueue)
                {
                    schedule.mSubCmdQueue.Add(this);
                    var len = ParallelUtils.Min(schedule.mWorkers.Length - schedule.mActiveWorkers, ParallelUtils.Max(1, mSize >> 1));
                    while (len > 0 && schedule.StartWorker() != null)
                    {
                        len--;
                    }
                }
                //worker.mExecuting = this;
                //ThreadPool.QueueUserWorkItem(mSchedule.mSubExecutor, this);
            }

            internal override void Execute(IThreadWorker worker)
            {
                //bool finish = false;
#if PROFILING
                Profiler.BeginSample(desc/*, context*/);
#endif
                int starti, count;
                lock (mLock)
                {
                    starti = mExecIndex;
                    count = mExecIndex + mBatchNum;
                    if (count > mSize || count < 0)
                        count = mSize;
                    mExecIndex = count;
                }
                //int count = Interlocked.Add(ref mExecIndex, mBatchNum);
                //int starti = count - mBatchNum;
                //count = count < mSize ? count : mSize;
                if (count > starti)
                {
                    try
                    {
                        for (int i = starti; mCompleteSize < mSize && i < count; i++)
                        {
                            mCmd.Execute(mList[i]);
                        }
                        Interlocked.Add(ref mCompleteSize, count - starti);
                    }
                    catch (System.Exception e)
                    {
                        Interlocked.Exchange(ref mCompleteSize, mSize);
                        Debug.LogException(e
#if PROFILING
                            , context
#endif
                            );
                        Debug.LogErrorFormat("[Parallel] execute prallel cmd ({0}) error.", mCmd.ToString());
                        //finish = !isComplete;
                    }
#if PROFILING
                    if (isComplete)
                        EndSample(false);
#endif
                }
                //finally
                //{
                //    threadId = 0;
                //}
                //if (finish && mCmd is ICompleteParallelFor)
                //    ((ICompleteParallelFor)mCmd).OnParallelForComplete();

#if PROFILING
                Profiler.EndSample();
                // if (finish)
                //     Debug.LogFormat("Complete \"{0}\" (time: {1:0.######}sec, batch: {2}/{3})",
                //         desc, (System.DateTime.UtcNow.Ticks - start_time) * tick_2_seconds, mBatchNum, mSize);
#endif
            }

#if PROFILING
            public override string ToString()
            {
                return desc;
            }
#endif
        }

        struct TaskInstruction : IYieldInstruction
        {
            Task mTask;

            internal TaskInstruction(Task task)
            {
                mTask = task;
            }

            public bool YieldContinue()
            {
                return mTask == null || mTask.IsCanceled || mTask.IsCompleted;
            }
        }

        /// <summary>
        /// yield
        /// </summary>
        private class YieldCmd : BaseCommand
        {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            internal class Buffer: Buffer<YieldCmd, IYieldCommand, bool>
            {
                internal Buffer(int size) : base(size) { }
                protected override YieldCmd NewCommand(int id, IYieldCommand cmd, bool mainThread, BaseCommand dependent)
                {
                    return new YieldCmd(id, cmd, mainThread, dependent);
                }
                protected override void Init(YieldCmd inst, IYieldCommand cmd, bool mainThread)
                {
                    Interlocked.Exchange(ref inst.mComplete, 0);
                    inst.loops = 0;
                    inst.mCmd = cmd;
                    inst.onThread = mainThread ? ExecuteThread.MainThread : ExecuteThread.SubThread;
#if PROFILING
                    try
                    {
                        if (cmd is IDebugContext ctx)
                            inst.context = ctx.GetContext();
                        else
                            inst.context = cmd as Object;
                        inst.desc = cmd.ToString() ?? cmd.GetType().Name;
                    }
                    catch
                    {
                        inst.desc = cmd.GetType().Name;
                    }
#endif
                }

                protected override void Release(YieldCmd inst)
                {
                    inst.mCmd = null;
                    inst.mWaitCmd = null;
                    inst.mWaitProgress = null;
                    inst.mWaitCmdId = 0;
                    inst.mStack = null;
                    //inst.mSchedule = null;
#if PROFILING
                    inst.context = null;
#endif
                }
            }
#endif

            IYieldCommand mCmd;
            //ParallelDispatcher mSchedule;
            ExecuteThread onThread;

#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            int mWaitCmdId;
#endif
            IYieldInstruction mWaitCmd;
            IExecutableProgress mWaitProgress;
            IteratorStack mStack;
            int loops;
            int mComplete;
#if PROFILING
            string desc;
            Object context;
#endif
            internal override object GetCommandInstance()
            {
                return mCmd;
            }
            internal override ExecuteThread executeThread { get { return onThread; } }
            internal override bool isComplete => mComplete != 0;
            internal YieldCmd(int id, IYieldCommand cmd, bool onMainThread, BaseCommand dependent) : base(id, dependent)
            {
                mCmd = cmd;
                this.onThread = onMainThread ? ExecuteThread.MainThread : ExecuteThread.SubThread;

#if PROFILING
                try
                {
                    if (cmd is IDebugContext ctx)
                        context = ctx.GetContext();
                    else
                        context = cmd as Object;
                    desc = cmd.ToString() ?? cmd.GetType().Name;
                }
                catch
                {
                    desc = cmd.GetType().Name;
                }
#endif
            }

            internal override float GetExecutionProgress(ParallelDispatcher threading)
            {
                if (mCmd is IExecutableProgress proc)
                    return proc.Progress;
                if (loops < 5)
                    return loops * 0.2f + (mWaitProgress == null ? 0 : mWaitProgress.Progress * 0.2f);
                return 0.8f + (mWaitProgress == null ? 0 : mWaitProgress.Progress * 0.2f);
            }

            internal override void OnBreak(ParallelDispatcher dispatcher)
            {
                var wasComplete = Interlocked.CompareExchange(ref mComplete, 1, 0);
                if (wasComplete == 0)
                {
                    try
                    {
                        if (mCmd is IBreakCommandSignal)
                            ((IBreakCommandSignal)mCmd).OnBreak();
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogException(e);
                    }
#if PROFILING
                    EndSample(true);
#endif

//#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
//                    dispatcher.mYieldCmdsBuffer.Cache(this);
//#endif
                }
            }

           
            internal override void Schedule(ParallelDispatcher schedule)
            {
#if PROFILING
                ScheduleSample();
#endif
                //mSchedule = schedule;
                if (onThread == ExecuteThread.MainThread)
                {
                    lock (schedule.mMainCmdQueue)
                    {
                        schedule.mMainCmdQueue.Add(this);
                    }
                }
                else
                {
                    lock (schedule.mSubCmdQueue)
                    {
                        schedule.mSubCmdQueue.Add(this);
                        schedule.StartWorker();
                    }
                }
            }

            void GetWaitCmd(IEnumerator iter)
            {
                var v = iter.Current;
                if (v is ICommandHandler)
                {
                    var handle = ((ICommandHandler)v).GetHandler();
                    var cmd = handle.cmd;
#if CHECK_CMD_RECURSIVE
                    if (cmd == mCmd && mCmd != null)
                        throw new System.Exception(string.Format("等待任务({0})为自身, 这将造成死锁", mCmd));
                    else if (handle.IsDependentOn(this))
                        throw new System.Exception(string.Format("等待任务({0})依赖于当前任务({1}), 这将造成死锁", handle.cmd, mCmd));
#endif
                    if (cmd == null)
                    {
                        mWaitCmd = null;
                        mWaitProgress = null;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                        mWaitCmdId = 0;
#endif
                    }
                    else
                    {
                        mWaitCmd = cmd;
                        mWaitProgress = cmd.GetCommandInstance() as IExecutableProgress;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                        mWaitCmdId = cmd.id;
#endif
                    }
                }
                else if (v is IYieldInstruction)
                {
                    mWaitCmd = (IYieldInstruction)v;
                    mWaitProgress = v as IExecutableProgress;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                    mWaitCmdId = 0;
#endif
                }
                else if (v is IEnumerator)
                {
                    if (mStack.Count >= ITERATOR_STACK_SIZE)
                        throw new System.Exception(string.Format("yield action stack overflow. (stack size: {0})", ITERATOR_STACK_SIZE));
                    mStack.Push((IEnumerator)v);
                    mWaitCmd = null;
                    mWaitProgress = null;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                    mWaitCmdId = 0;
#endif
                }
                else if(v is Task)
                {
                    mWaitCmd = new TaskInstruction((Task)v);
                    mWaitProgress = null;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                    mWaitCmdId = 0;
#endif
                }
                else
                {
                    mWaitCmd = null;
                    mWaitProgress = null;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                    mWaitCmdId = 0;
#endif
                }
            }

#if PROFILING
            int profId = -1;
            void ProfBeginSample(ref bool profiler)
            {
                if (!profiler && ParallelUnity.frameCount != profId)
                {
                    profId = ParallelUnity.frameCount;
                    profiler = true;
                    Profiler.BeginSample(desc, context);
                }
            }

            void ProfEndSample(ref bool profiler)
            {
                if (profiler)
                {
                    Profiler.EndSample();
                    profiler = false;
                }
            }
#endif

            bool ContinueWaitCmd()
            {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                if (mWaitCmd is BaseCommand bcmd)
                    return bcmd.id != mWaitCmdId || mWaitCmd.YieldContinue();
                else
                    return mWaitCmd.YieldContinue();
#else
                return mWaitCmd.YieldContinue();
#endif
            }

            internal override void Execute(IThreadWorker worker)
            {
                //loops++;
#if PROFILING
                bool profile = false;
                //Profiler.BeginSample(desc, context);
#endif
                bool act = true;
                bool broken = false;
                try
                {
                    if (mComplete != 0)
                    {
                        act = false;
                    }
                    else if (mWaitCmd != null && !ContinueWaitCmd())
                    {
                        act = true;
                    }
                    else if (mStack == null)
                    {
#if PROFILING
                        ProfBeginSample(ref profile);
#endif
                        loops++;
                        var iter = mCmd.Execute();
                        if (iter == null)
                        {
                            act = false;
                        }
                        else
                        {
                            mStack = worker.Dispatcher.GetIteratorStack();
                            mStack.Push(iter);
                            GetWaitCmd(iter);
                        }
                    }
                    else if (mStack.Count > 0)
                    {
#if PROFILING
                        ProfBeginSample(ref profile);
#endif
                        loops++;
                        var iter = mStack.Peek();
                        if (iter.MoveNext())
                        {
                            GetWaitCmd(iter);
                        }
                        else
                        {
                            mStack.Pop();
                            mWaitCmd = null;
                        }
                    }
                    else
                    {
                        mWaitCmd = null;
                        act = false;
                    }

                }
                catch (System.Exception e)
                {
                    act = false;
                    broken = true;
                    Debug.LogException(e);
                }
                if (!act)
                {
                    mWaitCmd = null;
                    mWaitProgress = null;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                    mWaitCmdId = 0;
#endif
                    if (mStack != null)
                    {
                        //while (mStack.Count > 0)
                        //{
                        //    var it = mStack.Pop().Current;
                        //    BaseCommand wait;
                        //    if (it is ICommandHandler chandler)
                        //    {
                        //        wait = chandler.GetHandler().cmd;
                        //    }
                        //    else
                        //    {
                        //        wait = null;
                        //        //wait = it as BaseCommand;
                        //    }
                        //    if (wait != null && !wait.isComplete && wait.IsBreakable(true))
                        //    {
                        //        wait.OnBreak(worker.Dispatcher);
                        //    }
                        //}
                        worker.Dispatcher.ReleaseStack(mStack);
                        mStack = null;
                    }
#if PROFILING
                    ProfEndSample(ref profile);
                    EndSample(broken);
#endif
                    if (broken)
                    {
                        OnBreak(worker.Dispatcher);
                    }
                    else
                    {
                        var wasComplete = Interlocked.CompareExchange(ref mComplete, 1, 0);
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                        if (wasComplete == 0)
                            worker.Dispatcher.mYieldCmdsBuffer.Cache(this);
#endif
                    }

                }
#if PROFILING
                else
                    ProfEndSample(ref profile);
#endif
            }

#if PROFILING
            public override string ToString()
            {
                return desc;
            }
#endif
        }

        private class LoopCmd : BaseCommand
        {
            ILoopCommand mCmd;
            ExecuteThread mExecThread;
            bool mComplete;
#if PROFILING
            string desc;
            Object context;
#endif
            internal override bool isComplete => mComplete;
            internal LoopCmd(int id, ILoopCommand cmd, bool mainThread, BaseCommand dependent) : base(id, dependent)
            {
                this.mCmd = cmd;
                mExecThread = mainThread ? ExecuteThread.MainThread : ExecuteThread.SubThread;
#if PROFILING
                try
                {
                    if (cmd is IDebugContext)
                        context = ((IDebugContext)cmd).GetContext();
                    else
                        context = cmd as Object;
                    desc = cmd.ToString() ?? cmd.GetType().Name;
                }
                catch
                {
                    desc = cmd.GetType().Name;
                }
#endif
            }
            internal override object GetCommandInstance()
            {
                return mCmd;
            }
            internal override ExecuteThread executeThread { get { return mExecThread; } }

            internal override void Execute(IThreadWorker worker)
            {

#if PROFILING
                Profiler.BeginSample(desc/*, context*/);
#endif
                try
                {
                    //threadId = worker.threadId;
                    if (!mComplete)
                        mCmd.Execute(((ILoopWorker)worker).deltaTime);
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e
#if PROFILING
                        , context
#endif
                        );
                }
                //finally
                //{
                //    threadId = 0;
                //}
#if PROFILING
                Profiler.EndSample();
                if (isComplete)
                    EndSample(false);
#endif
            }

            internal override float GetExecutionProgress(ParallelDispatcher threading)
            {
                return mComplete ? 1f : (mCmd is IExecutableProgress proc ? proc.Progress : 0.01f);
            }

            internal override void OnBreak(ParallelDispatcher dispatcher)
            {
                mComplete = true;
                try
                {
                    if (mCmd is IBreakCommandSignal)
                        ((IBreakCommandSignal)mCmd).OnBreak();
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                }
#if PROFILING
                EndSample(true);
#endif
            }

            internal override void Schedule(ParallelDispatcher dispatcher)
            {
#if PROFILING
                ScheduleSample();
#endif
                if (mExecThread == ExecuteThread.MainThread)
                {
                    lock (dispatcher.mMainCmdQueue)
                    {
                        dispatcher.mMainCmdQueue.Add(this);
                    }
                }
                else
                {
                    LoopWorker worker = dispatcher.GetLoopWorker();
                    worker.Join(this);
                    //threading.mLooper.Join(this);
                    //lock (threading.mLock)
                    //{
                    //    threading.StartLooperThread();
                    //}
                }
            }

            public override string ToString()
            {
                return mCmd.ToString();
            }
        }

        private class WaitCmd : BaseCommand
        {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            internal class Buffer : Buffer<WaitCmd, ICommand, bool>
            {
                internal Buffer(int size) : base(size) { }

                protected override WaitCmd NewCommand(int id, ICommand cmd, bool mainThread, BaseCommand dependent)
                {
                    return new WaitCmd(id, dependent);
                }
                protected override void Init(WaitCmd inst, ICommand cmd, bool mainThread)
                {
                    //inst.mComplete = false;
                    Interlocked.Exchange(ref inst.mComplete, 0);
                }
                protected override void Release(WaitCmd inst)
                {
                    //inst.mComplete = true;
                    var lst = inst.mWaitTargets;
                    if (lst != null)
                        lst.Cleanup();
                }
            }
#endif
#if PROFILING
            string mark;
#endif
            //bool mComplete;
            int mComplete;
            internal CascadeList<Handler> mWaitTargets;
            //SubThreading mSchedule;
            internal override ExecuteThread executeThread { get { return ExecuteThread.SubThread; } }
            internal override bool isComplete => mComplete != 0;
            internal WaitCmd(int id, BaseCommand depedent) : base(id, depedent)
            {
                //mWaitTargets = new RandomList<BaseCommand>(4);
                //mLock = new object();
            }
            internal object GetSyncLock() { return this; }
            internal override object GetCommandInstance()
            {
                return this;
            }
            internal override bool IsReady
            {
                get
                {
                    var baseCmd = dependentOn;
                    if (baseCmd != null && !baseCmd.isComplete)
                        return false;
                    lock (GetSyncLock())
                    {
                        if (mWaitTargets != null)
                        {
                            for (int i = mWaitTargets.Count - 1; i >= 0; i--)
                            {
                                var wait = mWaitTargets[i].cmd;
                                if (wait != null && !wait.isComplete)
                                    return false;
                                mWaitTargets.RemoveAt(i);
                            }
                        }
                    }
                    return true;
                }
            }

            internal override float GetExecutionProgress(ParallelDispatcher schedule)
            {
                return mComplete;// mComplete ? 1f : 0;
            }

            internal override void OnBreak(ParallelDispatcher dispatcher)
            {
                var wasComplete = Interlocked.CompareExchange(ref mComplete, 1, 0);
                //mComplete = true;
#if PROFILING
                if (wasComplete == 0)
                    EndSample(true);
#endif

//#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
//                if (wasComplete == 0)
//                    dispatcher.mWaitCmdsBuffer.Cache(this);
//#endif
            }

            internal override void Schedule(ParallelDispatcher schedule)
            {
                var wasComplete = Interlocked.CompareExchange(ref mComplete, 1, 0);
#if PROFILING
                if (wasComplete == 0)
                    EndSample(false);
#endif
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                if (wasComplete == 0)
                    schedule.mWaitCmdsBuffer.Cache(this);
#endif
            }

            internal override void Execute(IThreadWorker worker)
            {

            }


            internal void Join(Handler cmd)
            {
                lock (GetSyncLock())
                {
                    if (mWaitTargets == null)
                        mWaitTargets = new CascadeList<Handler>(4);
                    mWaitTargets.Add(cmd);
                }
            }

            internal void GetAllJoinedCommands(HashSet<BaseCommand> cmds, bool includeDependent)
            {
                if (isComplete || !cmds.Add(this))
                    return;
                lock (GetSyncLock())
                {
                    var dependent = dependentOn;
                    if (dependent != null && !dependent.isComplete)
                    {
                        if (includeDependent && dependent is WaitCmd wcmd)
                            wcmd.GetAllJoinedCommands(cmds, includeDependent);
                        else
                            cmds.Add(dependent);
                    }
                    if (mWaitTargets != null)
                    {
                        for (int i = mWaitTargets.Count - 1; i >= 0; i--)
                        {
                            var cmd = mWaitTargets[i].cmd;
                            if (cmd == null || cmd.isComplete)
                            {
                                mWaitTargets.RemoveAt(i);
                                continue;
                            }
                            else if (includeDependent && cmd is WaitCmd wcmd)
                                wcmd.GetAllJoinedCommands(cmds, includeDependent);
                            else
                                cmds.Add(cmd);
                        }
                    }
                }
            }

            public void SetInfo(string info)
            {
#if PROFILING
                this.mark = string.IsNullOrEmpty(info) ? "<unnamed>" : info;
#endif
            }

#if PROFILING
            public override string ToString()
            {
                return mark;
            }
#endif
        }
    }
}